<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!-->
<html class="no-js" lang="en">
<!--<![endif]-->
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Operations on sets - The .NET Core ORM Cookbook</title>
    <link rel="shortcut icon" href="favicon.ico">
    <link rel="stylesheet" href="css/theme.css" type="text/css" />
    <link rel="stylesheet" href="css/theme_colors.css" type="text/css" />
    <link rel="stylesheet" href="css/styles/vs.css">
    <link rel="stylesheet" href="css/font-awesome.4.5.0.min.css">
</head>
<body role="document">
    <div class="grid-for-nav">
        <nav data-toggle="nav-shift" class="nav-side stickynav">
            <div class="side-nav-search">
                <a href="index.htm"><i class="fa fa-home"></i> The .NET Core ORM Cookbook</a>
                <div role="search">
                    <form id="search-form" class="form" action="Docnet_search.htm" method="get">
                        <input type="text" name="q" placeholder="Search docs" />
                    </form>
                </div>
            </div>
            <div class="menu menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
<ul>
<li class="tocentry"><a href="index.htm">Home</a>
</li>

<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="ORMs.htm">ORMs</a></span>
</li>
<li class="tocentry"><a href="FAQ.htm">FAQ</a>
</li>
<li class="tocentry">
<ul>
<li><span class="navigationgroup"><i class="fa fa-caret-down"></i> <a href="StandardCRUDscenarios.htm">Standard CRUD scenarios</a></span></li>
<li class="tocentry"><a href="SingleModelCrud.htm">Operations on a single entity type</a>
</li>
<li class="tocentry current"><a class="current" href="MultipleCrud.htm">Operations on sets</a>
<ul class="currentrelative">
<li class="tocentry"><a href="#scenario-prototype">Scenario Prototype</a></li>

<li class="tocentry"><a href="#ado.net">ADO.NET</a></li>

<li class="tocentry"><a href="#chain">Chain</a></li>

<li class="tocentry"><a href="#dapper">Dapper</a></li>

<li class="tocentry"><a href="#dbconnector">DbConnector</a></li>

<li class="tocentry"><a href="#entity-framework-6">Entity Framework 6</a></li>

<li class="tocentry"><a href="#entity-framework-core">Entity Framework Core</a></li>

<li class="tocentry"><a href="#linq-to-db">LINQ to DB</a></li>

<li class="tocentry"><a href="#llblgen-pro">LLBLGen Pro</a></li>

<li class="tocentry"><a href="#nhibernate">NHibernate</a></li>

<li class="tocentry"><a href="#repodb">RepoDb</a></li>

<li class="tocentry"><a href="#servicestack">ServiceStack</a></li>



</ul>
<li class="tocentry"><a href="ModelWithChildren.htm">Operations on a graph of multiple entity types</a>
</li>
<li class="tocentry"><a href="Immutable.htm">Operations on immutable entities</a>
</li>
<li class="tocentry"><a href="TryCrud.htm">Handling failures and exceptions</a>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="OperationswithaForeignKeybasedLookupTable.htm">Operations with a Foreign Key based Lookup Table</a></span>
</li>

</ul>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="Fetchingdatascenarios.htm">Fetching data scenarios</a></span>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="Advancedscenarios.htm">Advanced scenarios</a></span>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="Sortingscenarios.htm">Sorting scenarios</a></span>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="Storedprocedurescenarios.htm">Stored procedure scenarios</a></span>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="Auditingandhistoryscenarios.htm">Auditing and history scenarios</a></span>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="Multi-Tenancyscenarios.htm">Multi-Tenancy scenarios</a></span>
</li>
<li class="tocentry">
<span class="navigationgroup"><i class="fa fa-caret-right"></i> <a href="UnknownDatabasescenarios.htm">Unknown Database scenarios</a></span>
</li>
</ul>
				<div class="toc-footer">
					<span class="text-small">
						<hr/>
						<a href="https://github.com/FransBouma/DocNet" target="_blank">Made with <i class="fa fa-github"></i> DocNet</a>
					</span>
				</div>	
			</div>
            &nbsp;
        </nav>
        <section data-toggle="nav-shift" class="nav-content-wrap">
            <nav class="nav-top" role="navigation" aria-label="top navigation">
                <i data-toggle="nav-top" class="fa fa-bars"></i>
                <a href="index.htm">The .NET Core ORM Cookbook</a>
            </nav>
            <div class="nav-content">
                <div role="navigation" aria-label="breadcrumbs navigation">
                    <div class="breadcrumbs">
<ul><li><a href="index.htm">Home</a></li> / <li><a href="StandardCRUDscenarios.htm">Standard CRUD scenarios</a></li> / <li><a href="MultipleCrud.htm">Operations on sets</a></li></ul>
					
                    </div>
                    <hr />
                </div>
                <div role="main">
                    <div class="section">
<h1 id="crud-operations-on-multiple-objects">CRUD Operations on Multiple Objects<a class="headerlink" href="#crud-operations-on-multiple-objects" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h1>
<p>These scenarios demonstrate how to perform Create, Read, Update, and Delete operations on a collection of 100 objects. </p>
<p>If the ORM supports it, the operation should be performed with a single SQL statement.</p>
<h2 id="scenario-prototype">Scenario Prototype<a class="headerlink" href="#scenario-prototype" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public interface IMultipleCrudScenario&lt;TEmployeeSimple&gt;
   where TEmployeeSimple : class, IEmployeeSimple, new()
{
    /// &lt;summary&gt;
    /// Delete a collection of Employee rows.
    /// &lt;/summary&gt;
    void DeleteBatch(IList&lt;TEmployeeSimple&gt; employees);

    /// &lt;summary&gt;
    /// Delete a collection of Employee rows by key.
    /// &lt;/summary&gt;
    void DeleteBatchByKey(IList&lt;int&gt; employeeKeys);

    /// &lt;summary&gt;
    /// Gets a collection of Employee rows by their name. Assume the name is not unique.
    /// &lt;/summary&gt;
    IList&lt;TEmployeeSimple&gt; FindByLastName(string lastName);

    /// &lt;summary&gt;
    /// Insert a collection of Employee rows.
    /// &lt;/summary&gt;
    void InsertBatch(IList&lt;TEmployeeSimple&gt; employees);

    /// &lt;summary&gt;
    /// Insert a collection of Employee rows, returning the newly created keys.
    /// &lt;/summary&gt;
    IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;TEmployeeSimple&gt; employees);

    /// &lt;summary&gt;
    /// Insert a collection of Employee rows, returning the newly created rows.
    /// &lt;/summary&gt;
    /// &lt;remarks&gt;This MAY return the original objects or new objects.&lt;/remarks&gt;
    IList&lt;TEmployeeSimple&gt; InsertBatchReturnRows(IList&lt;TEmployeeSimple&gt; employees);

    /// &lt;summary&gt;
    /// Insert a collection of Employee rows and update the original objects with the new keys.
    /// &lt;/summary&gt;
    void InsertBatchWithRefresh(IList&lt;TEmployeeSimple&gt; employees);

    /// &lt;summary&gt;
    /// Update a collection of Employee rows.
    /// &lt;/summary&gt;
    void UpdateBatch(IList&lt;TEmployeeSimple&gt; employees);
}
</code></pre>

<h2 id="ado.net">ADO.NET<a class="headerlink" href="#ado.net" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">    public class MultipleCrudScenario : SqlServerScenarioBase, IMultipleCrudScenario&lt;EmployeeSimple&gt;
    {
        public MultipleCrudScenario(string connectionString) : base(connectionString)
        { }

        public void DeleteBatch(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var keyList = string.Join(&quot;, &quot;, employees.Select(x =&gt; x.EmployeeKey));
            var sql = $&quot;DELETE FROM HR.Employee WHERE EmployeeKey IN ({keyList});&quot;;

            using (var con = OpenConnection())
            using (var cmd = new SqlCommand(sql, con))
                cmd.ExecuteNonQuery();
        }

        public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
        {
            if (employeeKeys == null || employeeKeys.Count == 0)
                throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

            var keyList = string.Join(&quot;, &quot;, employeeKeys);
            var sql = $&quot;DELETE FROM HR.Employee WHERE EmployeeKey IN ({keyList});&quot;;

            using (var con = OpenConnection())
            using (var cmd = new SqlCommand(sql, con))
                cmd.ExecuteNonQuery();
        }

        public IList&lt;EmployeeSimple&gt; FindByLastName(string lastName)
        {
            const string sql = &quot;SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.OfficePhone, e.CellPhone, e.EmployeeClassificationKey FROM HR.EmployeeDetail e WHERE e.LastName = @LastName&quot;;

            using (var con = OpenConnection())
            using (var cmd = new SqlCommand(sql, con))
            {
                cmd.Parameters.AddWithValue(&quot;@LastName&quot;, lastName);

                var results = new List&lt;EmployeeSimple&gt;();

                using (var reader = cmd.ExecuteReader())
                    while (reader.Read())
                        results.Add(new EmployeeSimple(reader));

                return results;
            }
        }

        public void InsertBatch(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder(@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
VALUES &quot;);

            for (var i = 0; i &lt; employees.Count; i++)
            {
                if (i != 0)
                    sql.AppendLine(&quot;,&quot;);
                sql.Append($&quot;(@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i})&quot;);
            }
            sql.AppendLine(&quot;;&quot;);

            //No transaction is needed because a single SQL statement is used.
            using (var con = OpenConnection())
            using (var cmd = new SqlCommand(sql.ToString(), con))
            {
                for (var i = 0; i &lt; employees.Count; i++)
                {
                    cmd.Parameters.AddWithValue($&quot;@FirstName_{i}&quot;, employees[i].FirstName);
                    cmd.Parameters.AddWithValue($&quot;@MiddleName_{i}&quot;, (object?)employees[i].MiddleName ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@LastName_{i}&quot;, employees[i].LastName);
                    cmd.Parameters.AddWithValue($&quot;@Title_{i}&quot;, (object?)employees[i].Title ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@OfficePhone_{i}&quot;, (object?)employees[i].OfficePhone ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@CellPhone_{i}&quot;, (object?)employees[i].CellPhone ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@EmployeeClassificationKey_{i}&quot;, employees[i].EmployeeClassificationKey);
                }
                cmd.ExecuteNonQuery();
            }
        }

        public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder(@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
OUTPUT Inserted.EmployeeKey
VALUES &quot;);

            for (var i = 0; i &lt; employees.Count; i++)
            {
                if (i != 0)
                    sql.AppendLine(&quot;,&quot;);
                sql.Append($&quot;(@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i})&quot;);
            }
            sql.AppendLine(&quot;;&quot;);

            //No transaction is needed because a single SQL statement is used.
            using (var con = OpenConnection())
            using (var cmd = new SqlCommand(sql.ToString(), con))
            {
                for (var i = 0; i &lt; employees.Count; i++)
                {
                    cmd.Parameters.AddWithValue($&quot;@FirstName_{i}&quot;, employees[i].FirstName);
                    cmd.Parameters.AddWithValue($&quot;@MiddleName_{i}&quot;, (object?)employees[i].MiddleName ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@LastName_{i}&quot;, employees[i].LastName);
                    cmd.Parameters.AddWithValue($&quot;@Title_{i}&quot;, (object?)employees[i].Title ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@OfficePhone_{i}&quot;, (object?)employees[i].OfficePhone ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@CellPhone_{i}&quot;, (object?)employees[i].CellPhone ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@EmployeeClassificationKey_{i}&quot;, employees[i].EmployeeClassificationKey);
                }
                var result = new List&lt;int&gt;();
                using (var reader = cmd.ExecuteReader())
                    while (reader.Read())
                        result.Add(reader.GetInt32(0));
                return result;
            }
        }

        public IList&lt;EmployeeSimple&gt; InsertBatchReturnRows(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder(@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
OUTPUT Inserted.EmployeeKey, Inserted.FirstName, Inserted.MiddleName, Inserted.LastName, Inserted.Title, Inserted.OfficePhone, Inserted.CellPhone, Inserted.EmployeeClassificationKey
VALUES &quot;);

            for (var i = 0; i &lt; employees.Count; i++)
            {
                if (i != 0)
                    sql.AppendLine(&quot;,&quot;);
                sql.Append($&quot;(@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i})&quot;);
            }
            sql.AppendLine(&quot;;&quot;);

            //No transaction is needed because a single SQL statement is used.
            using (var con = OpenConnection())
            using (var cmd = new SqlCommand(sql.ToString(), con))
            {
                for (var i = 0; i &lt; employees.Count; i++)
                {
                    cmd.Parameters.AddWithValue($&quot;@FirstName_{i}&quot;, employees[i].FirstName);
                    cmd.Parameters.AddWithValue($&quot;@MiddleName_{i}&quot;, (object?)employees[i].MiddleName ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@LastName_{i}&quot;, employees[i].LastName);
                    cmd.Parameters.AddWithValue($&quot;@Title_{i}&quot;, (object?)employees[i].Title ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@OfficePhone_{i}&quot;, (object?)employees[i].OfficePhone ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@CellPhone_{i}&quot;, (object?)employees[i].CellPhone ?? DBNull.Value);
                    cmd.Parameters.AddWithValue($&quot;@EmployeeClassificationKey_{i}&quot;, employees[i].EmployeeClassificationKey);
                }
                var result = new List&lt;EmployeeSimple&gt;();
                using (var reader = cmd.ExecuteReader())
                    while (reader.Read())
                        result.Add(new EmployeeSimple(reader));
                return result;
            }
        }

        public void InsertBatchWithRefresh(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder();

            //In order to ensure the right objects are refreshed, each object is inserted separately.
            //If we returned them all at the same time, they might not come back in the same order.
            for (var i = 0; i &lt; employees.Count; i++)
            {
                sql.AppendLine($@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
OUTPUT Inserted.EmployeeKey, Inserted.FirstName, Inserted.MiddleName, Inserted.LastName, Inserted.Title, Inserted.OfficePhone, Inserted.CellPhone, Inserted.EmployeeClassificationKey
VALUES (@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i});&quot;);
            }

            //A transaction is needed because this example uses multiple SQL statements.
            using (var con = OpenConnection())
            using (var trans = con.BeginTransaction())
            {
                using (var cmd = new SqlCommand(sql.ToString(), con, trans))
                {
                    for (var i = 0; i &lt; employees.Count; i++)
                    {
                        cmd.Parameters.AddWithValue($&quot;@FirstName_{i}&quot;, employees[i].FirstName);
                        cmd.Parameters.AddWithValue($&quot;@MiddleName_{i}&quot;, (object?)employees[i].MiddleName ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@LastName_{i}&quot;, employees[i].LastName);
                        cmd.Parameters.AddWithValue($&quot;@Title_{i}&quot;, (object?)employees[i].Title ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@OfficePhone_{i}&quot;, (object?)employees[i].OfficePhone ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@CellPhone_{i}&quot;, (object?)employees[i].CellPhone ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@EmployeeClassificationKey_{i}&quot;, employees[i].EmployeeClassificationKey);
                    }

                    using (var reader = cmd.ExecuteReader())
                    {
                        for (var i = 0; i &lt; employees.Count; i++)
                        {
                            reader.Read();
                            employees[i].Refresh(reader);
                            reader.NextResult(); //each row is coming back as a separate result set
                        }
                    }
                }
                trans.Commit();
            }
        }

        public void UpdateBatch(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder();

            for (var i = 0; i &lt; employees.Count; i++)
            {
                sql.AppendLine($@&quot;UPDATE HR.Employee
SET FirstName = @FirstName_{i},
    MiddleName = @MiddleName_{i},
    LastName = @LastName_{i},
    Title = @Title_{i},
    OfficePhone = @OfficePhone_{i},
    CellPhone = @CellPhone_{i},
    EmployeeClassificationKey = @EmployeeClassificationKey_{i}
WHERE EmployeeKey = @EmployeeKey_{i};&quot;);
            }

            //A transaction is needed because this example uses multiple SQL statements.
            using (var con = OpenConnection())
            using (var trans = con.BeginTransaction())
            {
                using (var cmd = new SqlCommand(sql.ToString(), con, trans))
                {
                    for (var i = 0; i &lt; employees.Count; i++)
                    {
                        cmd.Parameters.AddWithValue($&quot;@EmployeeKey_{i}&quot;, employees[i].EmployeeKey);
                        cmd.Parameters.AddWithValue($&quot;@FirstName_{i}&quot;, employees[i].FirstName);
                        cmd.Parameters.AddWithValue($&quot;@MiddleName_{i}&quot;, (object?)employees[i].MiddleName ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@LastName_{i}&quot;, employees[i].LastName);
                        cmd.Parameters.AddWithValue($&quot;@Title_{i}&quot;, (object?)employees[i].Title ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@OfficePhone_{i}&quot;, (object?)employees[i].OfficePhone ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@CellPhone_{i}&quot;, (object?)employees[i].CellPhone ?? DBNull.Value);
                        cmd.Parameters.AddWithValue($&quot;@EmployeeClassificationKey_{i}&quot;, employees[i].EmployeeClassificationKey);
                    }
                    cmd.ExecuteNonQuery();
                }
                trans.Commit();
            }
        }
    }
</code></pre>

<h2 id="chain">Chain<a class="headerlink" href="#chain" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : IMultipleCrudScenario&lt;EmployeeSimple&gt;
{
    const string EmployeeTableName = &quot;HR.Employee&quot;;
    readonly SqlServerDataSource m_DataSource;

    public MultipleCrudScenario(SqlServerDataSource dataSource)
    {
        m_DataSource = dataSource;
    }

    public void DeleteBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        m_DataSource.DeleteByKeyList(EmployeeTableName, employees.Select(x =&gt; x.EmployeeKey)).Execute();
    }

    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if (employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

        m_DataSource.DeleteByKeyList(EmployeeTableName, employeeKeys).Execute();
    }

    public IList&lt;EmployeeSimple&gt; FindByLastName(string lastName)
    {
        return m_DataSource.From&lt;EmployeeSimple&gt;(new { lastName }).ToCollection().Execute();
    }

    public void InsertBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        m_DataSource.InsertBatch((IReadOnlyList&lt;EmployeeSimple&gt;)employees).Execute();
    }

    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        return m_DataSource.InsertBatch((IReadOnlyList&lt;EmployeeSimple&gt;)employees).ToInt32List().Execute();
    }

    public IList&lt;EmployeeSimple&gt; InsertBatchReturnRows(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        return m_DataSource.InsertBatch((IReadOnlyList&lt;EmployeeSimple&gt;)employees).ToCollection&lt;EmployeeSimple&gt;().Execute();
    }

    public void InsertBatchWithRefresh(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        //Chain does not support updating multiple rows at one time from objects
        //Use the WithRefresh() link to update the original object.
        using (var trans = m_DataSource.BeginTransaction())
        {
            foreach (var item in employees)
                trans.Insert(item).WithRefresh().Execute();

            trans.Commit();
        }
    }

    public void UpdateBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        //Chain does not support updating multiple rows at one time from objects
        using (var trans = m_DataSource.BeginTransaction())
        {
            foreach (var item in employees)
                trans.Update(item).Execute();

            trans.Commit();
        }
    }
}
</code></pre>

<h2 id="dapper">Dapper<a class="headerlink" href="#dapper" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">    public class MultipleCrudScenario : ScenarioBase, IMultipleCrudScenario&lt;EmployeeSimple&gt;
    {
        public MultipleCrudScenario(string connectionString) : base(connectionString)
        { }

        virtual public void DeleteBatch(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var keyList = string.Join(&quot;, &quot;, employees.Select(x =&gt; x.EmployeeKey));
            var sql = $&quot;DELETE FROM HR.Employee WHERE EmployeeKey IN ({keyList});&quot;;

            using (var con = OpenConnection())
                con.Execute(sql);
        }

        public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
        {
            if (employeeKeys == null || employeeKeys.Count == 0)
                throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

            var keyList = string.Join(&quot;, &quot;, employeeKeys);
            var sql = $&quot;DELETE FROM HR.Employee WHERE EmployeeKey IN ({keyList});&quot;;

            using (var con = OpenConnection())
                con.Execute(sql);
        }

        public IList&lt;EmployeeSimple&gt; FindByLastName(string lastName)
        {
            const string sql = &quot;SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.OfficePhone, e.CellPhone, e.EmployeeClassificationKey FROM HR.EmployeeDetail e WHERE e.LastName = @LastName&quot;;

            using (var con = OpenConnection())
                return con.Query&lt;EmployeeSimple&gt;(sql, new { lastName }).ToList();
        }

        virtual public void InsertBatch(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder(@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
VALUES &quot;);
            var parameters = new Dictionary&lt;string, object?&gt;();
            for (var i = 0; i &lt; employees.Count; i++)
            {
                if (i != 0)
                    sql.AppendLine(&quot;,&quot;);
                sql.Append($&quot;(@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i})&quot;);

                parameters[$&quot;@FirstName_{i}&quot;] = employees[i].FirstName;
                parameters[$&quot;@MiddleName_{i}&quot;] = employees[i].MiddleName;
                parameters[$&quot;@LastName_{i}&quot;] = employees[i].LastName;
                parameters[$&quot;@Title_{i}&quot;] = employees[i].Title;
                parameters[$&quot;@OfficePhone_{i}&quot;] = employees[i].OfficePhone;
                parameters[$&quot;@CellPhone_{i}&quot;] = employees[i].CellPhone;
                parameters[$&quot;@EmployeeClassificationKey_{i}&quot;] = employees[i].EmployeeClassificationKey;
            }
            sql.AppendLine(&quot;;&quot;);

            //No transaction is needed because a single SQL statement is used.
            using (var con = OpenConnection())
                con.Execute(sql.ToString(), parameters);
        }

        public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder(@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
OUTPUT Inserted.EmployeeKey
VALUES &quot;);

            var parameters = new Dictionary&lt;string, object?&gt;();
            for (var i = 0; i &lt; employees.Count; i++)
            {
                if (i != 0)
                    sql.AppendLine(&quot;,&quot;);
                sql.Append($&quot;(@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i})&quot;);

                parameters[$&quot;@FirstName_{i}&quot;] = employees[i].FirstName;
                parameters[$&quot;@MiddleName_{i}&quot;] = employees[i].MiddleName;
                parameters[$&quot;@LastName_{i}&quot;] = employees[i].LastName;
                parameters[$&quot;@Title_{i}&quot;] = employees[i].Title;
                parameters[$&quot;@OfficePhone_{i}&quot;] = employees[i].OfficePhone;
                parameters[$&quot;@CellPhone_{i}&quot;] = employees[i].CellPhone;
                parameters[$&quot;@EmployeeClassificationKey_{i}&quot;] = employees[i].EmployeeClassificationKey;
            }
            sql.AppendLine(&quot;;&quot;);

            //No transaction is needed because a single SQL statement is used.
            using (var con = OpenConnection())
                return con.Query&lt;int&gt;(sql.ToString(), parameters).ToList();
        }

        public IList&lt;EmployeeSimple&gt; InsertBatchReturnRows(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder(@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
OUTPUT Inserted.EmployeeKey, Inserted.FirstName, Inserted.MiddleName, Inserted.LastName, Inserted.Title, Inserted.OfficePhone, Inserted.CellPhone, Inserted.EmployeeClassificationKey
VALUES &quot;);

            var parameters = new Dictionary&lt;string, object?&gt;();
            for (var i = 0; i &lt; employees.Count; i++)
            {
                if (i != 0)
                    sql.AppendLine(&quot;,&quot;);
                sql.Append($&quot;(@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i})&quot;);

                parameters[$&quot;@FirstName_{i}&quot;] = employees[i].FirstName;
                parameters[$&quot;@MiddleName_{i}&quot;] = employees[i].MiddleName;
                parameters[$&quot;@LastName_{i}&quot;] = employees[i].LastName;
                parameters[$&quot;@Title_{i}&quot;] = employees[i].Title;
                parameters[$&quot;@OfficePhone_{i}&quot;] = employees[i].OfficePhone;
                parameters[$&quot;@CellPhone_{i}&quot;] = employees[i].CellPhone;
                parameters[$&quot;@EmployeeClassificationKey_{i}&quot;] = employees[i].EmployeeClassificationKey;
            }
            sql.AppendLine(&quot;;&quot;);

            //No transaction is needed because a single SQL statement is used.
            using (var con = OpenConnection())
                return con.Query&lt;EmployeeSimple&gt;(sql.ToString(), parameters).ToList();
        }

        public void InsertBatchWithRefresh(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder();

            //In order to ensure the right objects are refreshed, each object is inserted separately.
            //If we returned them all at the same time, they might not come back in the same order.
            var parameters = new Dictionary&lt;string, object?&gt;();
            for (var i = 0; i &lt; employees.Count; i++)
            {
                sql.AppendLine($@&quot;INSERT INTO HR.Employee
(FirstName, MiddleName, LastName, Title, OfficePhone, CellPhone, EmployeeClassificationKey)
OUTPUT Inserted.EmployeeKey, Inserted.FirstName, Inserted.MiddleName, Inserted.LastName, Inserted.Title, Inserted.OfficePhone, Inserted.CellPhone, Inserted.EmployeeClassificationKey
VALUES (@FirstName_{i}, @MiddleName_{i}, @LastName_{i}, @Title_{i}, @OfficePhone_{i}, @CellPhone_{i}, @EmployeeClassificationKey_{i});&quot;);

                parameters[$&quot;@FirstName_{i}&quot;] = employees[i].FirstName;
                parameters[$&quot;@MiddleName_{i}&quot;] = employees[i].MiddleName;
                parameters[$&quot;@LastName_{i}&quot;] = employees[i].LastName;
                parameters[$&quot;@Title_{i}&quot;] = employees[i].Title;
                parameters[$&quot;@OfficePhone_{i}&quot;] = employees[i].OfficePhone;
                parameters[$&quot;@CellPhone_{i}&quot;] = employees[i].CellPhone;
                parameters[$&quot;@EmployeeClassificationKey_{i}&quot;] = employees[i].EmployeeClassificationKey;
            }

            //A transaction is needed because this example uses multiple SQL statements.
            using (var con = OpenConnection())
            using (var trans = con.BeginTransaction())
            {
                var results = con.QueryMultiple(sql.ToString(), parameters, transaction: trans);

                for (var i = 0; i &lt; employees.Count; i++)
                {
                    var temp = results.ReadSingle&lt;EmployeeSimple&gt;(); //each row is coming back as a separate result set
                    employees[i].Refresh(temp);
                }
            }
        }

        virtual public void UpdateBatch(IList&lt;EmployeeSimple&gt; employees)
        {
            if (employees == null || employees.Count == 0)
                throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

            var sql = new StringBuilder();

            var parameters = new Dictionary&lt;string, object?&gt;();
            for (var i = 0; i &lt; employees.Count; i++)
            {
                sql.AppendLine($@&quot;UPDATE HR.Employee
SET FirstName = @FirstName_{i},
    MiddleName = @MiddleName_{i},
    LastName = @LastName_{i},
    Title = @Title_{i},
    OfficePhone = @OfficePhone_{i},
    CellPhone = @CellPhone_{i},
    EmployeeClassificationKey = @EmployeeClassificationKey_{i}
WHERE EmployeeKey = @EmployeeKey_{i};&quot;);

                parameters[$&quot;@EmployeeKey_{i}&quot;] = employees[i].EmployeeKey;
                parameters[$&quot;@FirstName_{i}&quot;] = employees[i].FirstName;
                parameters[$&quot;@MiddleName_{i}&quot;] = employees[i].MiddleName;
                parameters[$&quot;@LastName_{i}&quot;] = employees[i].LastName;
                parameters[$&quot;@Title_{i}&quot;] = employees[i].Title;
                parameters[$&quot;@OfficePhone_{i}&quot;] = employees[i].OfficePhone;
                parameters[$&quot;@CellPhone_{i}&quot;] = employees[i].CellPhone;
                parameters[$&quot;@EmployeeClassificationKey_{i}&quot;] = employees[i].EmployeeClassificationKey;
            }

            //A transaction is needed because this example uses multiple SQL statements.
            using (var con = OpenConnection())
            using (var trans = con.BeginTransaction())
            {
                con.Execute(sql.ToString(), parameters, transaction: trans);
                trans.Commit();
            }
        }
    }
</code></pre>

<div class="alert alert-info"><span class="alert-title"><i class="fa fa-info-circle"></i> Info</span><p>The repository methods are not normally virtual. This was done so that they could be overridden with better implementations as shown below.</p>
</div><h2 id="dbconnector">DbConnector<a class="headerlink" href="#dbconnector" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : ScenarioBase, IMultipleCrudScenario&lt;EmployeeSimple&gt;
{
    public MultipleCrudScenario(string connectionString) : base(connectionString)
    { }

    public void DeleteBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        var keyList = string.Join(&quot;, &quot;, employees.Select(x =&gt; x.EmployeeKey));
        var sql = $&quot;DELETE FROM HR.Employee WHERE EmployeeKey IN ({keyList});&quot;;

        DbConnector.NonQuery(sql).Execute();
    }

    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if (employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

        var keyList = string.Join(&quot;, &quot;, employeeKeys);
        var sql = $&quot;DELETE FROM HR.Employee WHERE EmployeeKey IN ({keyList});&quot;;

        DbConnector.NonQuery(sql).Execute();
    }

    public IList&lt;EmployeeSimple&gt; FindByLastName(string lastName)
    {
        const string sql = &quot;SELECT e.EmployeeKey, e.FirstName, e.MiddleName, e.LastName, e.Title, e.OfficePhone, e.CellPhone, e.EmployeeClassificationKey FROM HR.EmployeeDetail e WHERE e.LastName = @lastName&quot;;

        return DbConnector.ReadToList&lt;EmployeeSimple&gt;(sql, new { lastName }).Execute();
    }

    public void InsertBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || !employees.Any())
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        //Best approach for unlimited inserts since SQL server has parameter amount restrictions
        //https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server?redirectedfrom=MSDN&amp;view=sql-server-ver15
        DbConnector.Build&lt;int?&gt;(
                sql: @$&quot;INSERT INTO {EmployeeSimple.TableName}
                (
                    CellPhone,
                    EmployeeClassificationKey,
                    FirstName,
                    LastName,
                    MiddleName,
                    OfficePhone,
                    Title
                ) 
                VALUES (
                    @{nameof(EmployeeSimple.CellPhone)},
                    @{nameof(EmployeeSimple.EmployeeClassificationKey)},
                    @{nameof(EmployeeSimple.FirstName)},
                    @{nameof(EmployeeSimple.LastName)},
                    @{nameof(EmployeeSimple.MiddleName)},
                    @{nameof(EmployeeSimple.OfficePhone)},
                    @{nameof(EmployeeSimple.Title)}
                )&quot;,
                param: employees.First(),
                onExecute: (int? result, IDbExecutionModel em) =&gt;
                {
                    //Set the command
                    DbCommand command = em.Command;

                    //Execute first row.
                    em.NumberOfRowsAffected = command.ExecuteNonQuery();

                    //Set and execute remaining rows.
                    foreach (var emp in employees.Skip(1))
                    {
                        command.Parameters[nameof(EmployeeSimple.CellPhone)].Value = emp.CellPhone ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.EmployeeClassificationKey)].Value = emp.EmployeeClassificationKey;
                        command.Parameters[nameof(EmployeeSimple.FirstName)].Value = emp.FirstName ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.LastName)].Value = emp.LastName ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.MiddleName)].Value = emp.MiddleName ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.OfficePhone)].Value = emp.OfficePhone ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.Title)].Value = emp.Title ?? (object)DBNull.Value;

                        em.NumberOfRowsAffected += command.ExecuteNonQuery();
                    }

                    return em.NumberOfRowsAffected;
                }
            )
            .WithIsolationLevel(IsolationLevel.ReadCommitted)//Use a transaction
            .Execute();
    }

    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        string sql = @$&quot;INSERT INTO {EmployeeSimple.TableName}
                        (
                            CellPhone,
                            EmployeeClassificationKey,
                            FirstName,
                            LastName,
                            MiddleName,
                            OfficePhone,
                            Title
                        ) 
                        OUTPUT Inserted.EmployeeKey
                        VALUES (
                            @{nameof(EmployeeSimple.CellPhone)},
                            @{nameof(EmployeeSimple.EmployeeClassificationKey)},
                            @{nameof(EmployeeSimple.FirstName)},
                            @{nameof(EmployeeSimple.LastName)},
                            @{nameof(EmployeeSimple.MiddleName)},
                            @{nameof(EmployeeSimple.OfficePhone)},
                            @{nameof(EmployeeSimple.Title)}
                        )&quot;;

        //Best approach since SQL server has parameter amount restrictions
        //https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server?redirectedfrom=MSDN&amp;view=sql-server-ver15
        return DbConnector.ReadTo&lt;List&lt;int&gt;&gt;(
                 onInit: (cmds) =&gt;
                 {
                     foreach (var emp in employees)
                     {
                         cmds.Enqueue(cmd =&gt;
                         {
                             cmd.CommandText = sql;
                             cmd.CommandBehavior = CommandBehavior.SingleResult;
                             cmd.Parameters.AddFor(emp);
                         });
                     }
                 },
                 onLoad: (List&lt;int&gt; data, IDbExecutionModel em, DbDataReader odr) =&gt;
                 {
                     if (data == null)
                         data = new List&lt;int&gt;();

                     data.Add(odr.SingleOrDefault&lt;int&gt;(em.Token, em.JobCommand));

                     return data;
                 }
             )
            .WithIsolationLevel(IsolationLevel.ReadCommitted)//Use a transaction
            .Execute();
    }

    public IList&lt;EmployeeSimple&gt; InsertBatchReturnRows(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        string sql = @$&quot;INSERT INTO {EmployeeSimple.TableName}
                        (
                            CellPhone,
                            EmployeeClassificationKey,
                            FirstName,
                            LastName,
                            MiddleName,
                            OfficePhone,
                            Title
                        ) 
                        OUTPUT Inserted.EmployeeKey, Inserted.FirstName, Inserted.MiddleName, Inserted.LastName, Inserted.Title, Inserted.OfficePhone, Inserted.CellPhone, Inserted.EmployeeClassificationKey
                        VALUES (
                            @{nameof(EmployeeSimple.CellPhone)},
                            @{nameof(EmployeeSimple.EmployeeClassificationKey)},
                            @{nameof(EmployeeSimple.FirstName)},
                            @{nameof(EmployeeSimple.LastName)},
                            @{nameof(EmployeeSimple.MiddleName)},
                            @{nameof(EmployeeSimple.OfficePhone)},
                            @{nameof(EmployeeSimple.Title)}
                        )&quot;;

        //Best approach since SQL server has parameter amount restrictions
        //https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server?redirectedfrom=MSDN&amp;view=sql-server-ver15
        return DbConnector.ReadTo&lt;List&lt;EmployeeSimple&gt;&gt;(
                 onInit: (cmds) =&gt;
                 {
                     foreach (var emp in employees)
                     {
                         cmds.Enqueue(cmd =&gt;
                         {
                             cmd.CommandText = sql;
                             cmd.CommandBehavior = CommandBehavior.SingleResult;
                             cmd.Parameters.AddFor(emp);
                         });
                     }
                 },
                 onLoad: (List&lt;EmployeeSimple&gt; data, IDbExecutionModel em, DbDataReader odr) =&gt;
                 {
                     if (data == null)
                         data = new List&lt;EmployeeSimple&gt;();

                     data.Add(odr.SingleOrDefault&lt;EmployeeSimple&gt;(em.Token, em.JobCommand));

                     return data;
                 }
             )
            .WithIsolationLevel(IsolationLevel.ReadCommitted)//Use a transaction
            .Execute();
    }

    public void InsertBatchWithRefresh(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        string sql = @$&quot;INSERT INTO {EmployeeSimple.TableName}
                        (
                            CellPhone,
                            EmployeeClassificationKey,
                            FirstName,
                            LastName,
                            MiddleName,
                            OfficePhone,
                            Title
                        ) 
                        OUTPUT Inserted.EmployeeKey, Inserted.FirstName, Inserted.MiddleName, Inserted.LastName, Inserted.Title, Inserted.OfficePhone, Inserted.CellPhone, Inserted.EmployeeClassificationKey
                        VALUES (
                            @{nameof(EmployeeSimple.CellPhone)},
                            @{nameof(EmployeeSimple.EmployeeClassificationKey)},
                            @{nameof(EmployeeSimple.FirstName)},
                            @{nameof(EmployeeSimple.LastName)},
                            @{nameof(EmployeeSimple.MiddleName)},
                            @{nameof(EmployeeSimple.OfficePhone)},
                            @{nameof(EmployeeSimple.Title)}
                        )&quot;;

        //Best approach since SQL server has parameter amount restrictions
        //https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server?redirectedfrom=MSDN&amp;view=sql-server-ver15
        DbConnector.ReadTo&lt;bool&gt;(
                 onInit: (cmds) =&gt;
                 {
                     foreach (var emp in employees)
                     {
                         cmds.Enqueue(cmd =&gt;
                         {
                             cmd.CommandText = sql;
                             cmd.CommandBehavior = CommandBehavior.SingleResult;
                             cmd.Parameters.AddFor(emp);
                         });
                     }
                 },
                 onLoad: (bool result, IDbExecutionModel em, DbDataReader odr) =&gt;
                 {
                     employees[em.Index].Refresh(odr.SingleOrDefault&lt;EmployeeSimple&gt;(em.Token, em.JobCommand));
                     return true;
                 }
             )
            .WithIsolationLevel(IsolationLevel.ReadCommitted)//Use a transaction
            .Execute();
    }

    public void UpdateBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || !employees.Any())
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        //Best approach since SQL server has parameter amount restrictions
        //https://docs.microsoft.com/en-us/sql/sql-server/maximum-capacity-specifications-for-sql-server?redirectedfrom=MSDN&amp;view=sql-server-ver15
        DbConnector.Build&lt;int?&gt;(
                sql: @$&quot;UPDATE {EmployeeSimple.TableName}
                SET
                    CellPhone = @{nameof(EmployeeSimple.CellPhone)},
                    EmployeeClassificationKey = @{nameof(EmployeeSimple.EmployeeClassificationKey)},
                    FirstName = @{nameof(EmployeeSimple.FirstName)},
                    LastName = @{nameof(EmployeeSimple.LastName)},
                    MiddleName = @{nameof(EmployeeSimple.MiddleName)},
                    OfficePhone = @{nameof(EmployeeSimple.OfficePhone)},
                    Title = @{nameof(EmployeeSimple.Title)}
                WHERE EmployeeKey = @{nameof(EmployeeSimple.EmployeeKey)}&quot;,
                param: employees.First(),
                onExecute: (int? result, IDbExecutionModel em) =&gt;
                {
                    //Set the command
                    DbCommand command = em.Command;

                    //Execute first row.
                    em.NumberOfRowsAffected = command.ExecuteNonQuery();

                    //Set and execute remaining rows.
                    foreach (var emp in employees.Skip(1))
                    {
                        command.Parameters[nameof(EmployeeSimple.EmployeeKey)].Value = emp.EmployeeKey;
                        command.Parameters[nameof(EmployeeSimple.CellPhone)].Value = emp.CellPhone ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.EmployeeClassificationKey)].Value = emp.EmployeeClassificationKey;
                        command.Parameters[nameof(EmployeeSimple.FirstName)].Value = emp.FirstName ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.LastName)].Value = emp.LastName ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.MiddleName)].Value = emp.MiddleName ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.OfficePhone)].Value = emp.OfficePhone ?? (object)DBNull.Value;
                        command.Parameters[nameof(EmployeeSimple.Title)].Value = emp.Title ?? (object)DBNull.Value;

                        em.NumberOfRowsAffected += command.ExecuteNonQuery();
                    }

                    return em.NumberOfRowsAffected;
                }
            )
            .WithIsolationLevel(IsolationLevel.ReadCommitted)//Use a transaction
            .Execute();
    }
}
</code></pre>

<h3 id="dapper.contrib">Dapper.Contrib<a class="headerlink" href="#dapper.contrib" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h3>
<p>The Dapper.Contrib library can elimiante the boilerplate for some common scenarios. </p>
<pre><code class="cs">public class MultipleCrudScenarioContrib : MultipleCrudScenario
{
    public MultipleCrudScenarioContrib(string connectionString) : base(connectionString)
    { }

    override public void DeleteBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var con = OpenConnection())
            con.Delete(employees);
    }

    override public void InsertBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var con = OpenConnection())
            con.Insert(employees);
    }

    override public void UpdateBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var con = OpenConnection())
            con.Update(employees);
    }
}
</code></pre>

<h2 id="entity-framework-6">Entity Framework 6<a class="headerlink" href="#entity-framework-6" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : IMultipleCrudScenario&lt;Employee&gt;
{
    private Func&lt;OrmCookbookContext&gt; CreateDbContext;

    public MultipleCrudScenario(Func&lt;OrmCookbookContext&gt; dBContextFactory)
    {
        CreateDbContext = dBContextFactory;
    }

    public void DeleteBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Entry(employee).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }

    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if (employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

        using (var context = CreateDbContext())
        {
            foreach (var key in employeeKeys)
                context.Entry(new Employee() { EmployeeKey = key }).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }

    public IList&lt;Employee&gt; FindByLastName(string lastName)
    {
        using (var context = CreateDbContext())
            return context.Employee.Where(ec =&gt; ec.LastName == lastName).ToList();
    }

    public void InsertBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employee.Add(employee);
            context.SaveChanges();
        }
    }

    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employee.Add(employee);
            context.SaveChanges();
        }

        return employees.Select(x =&gt; x.EmployeeKey).ToList();
    }

    public IList&lt;Employee&gt; InsertBatchReturnRows(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employee.Add(employee);
            context.SaveChanges();
        }

        return employees;
    }

    public void InsertBatchWithRefresh(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employee.Add(employee);
            context.SaveChanges();
        }
    }

    public void UpdateBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Entry(employee).State = EntityState.Modified;
            context.SaveChanges();
        }
    }
}
</code></pre>

<h2 id="entity-framework-core">Entity Framework Core<a class="headerlink" href="#entity-framework-core" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : IMultipleCrudScenario&lt;Employee&gt;
{
    private Func&lt;OrmCookbookContext&gt; CreateDbContext;

    public MultipleCrudScenario(Func&lt;OrmCookbookContext&gt; dBContextFactory)
    {
        CreateDbContext = dBContextFactory;
    }

    public void DeleteBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Entry(employee).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }

    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if (employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

        using (var context = CreateDbContext())
        {
            foreach (var key in employeeKeys)
                context.Entry(new Employee() { EmployeeKey = key }).State = EntityState.Deleted;
            context.SaveChanges();
        }
    }

    public IList&lt;Employee&gt; FindByLastName(string lastName)
    {
        using (var context = CreateDbContext())
            return context.Employees.Where(ec =&gt; ec.LastName == lastName).ToList();
    }

    public void InsertBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employees.Add(employee);
            context.SaveChanges();
        }
    }

    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employees.Add(employee);
            context.SaveChanges();
        }

        return employees.Select(x =&gt; x.EmployeeKey).ToList();
    }

    public IList&lt;Employee&gt; InsertBatchReturnRows(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employees.Add(employee);
            context.SaveChanges();
        }

        return employees;
    }

    public void InsertBatchWithRefresh(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Employees.Add(employee);
            context.SaveChanges();
        }
    }

    public void UpdateBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var context = CreateDbContext())
        {
            foreach (var employee in employees)
                context.Entry(employee).State = EntityState.Modified;
            context.SaveChanges();
        }
    }
}
</code></pre>

<p>It should be noted that performance of the batch update and delete operations is pretty low, Entity Framework Core will Update or Delete every record one by one. </p>
<p>There is a third-party library can implement high-performance batch operations, which is named Zack.EFCore.Batch. It can be obtained from https://github.com/yangzhongke/Zack.EFCore.Batch.</p>
<h2 id="linq-to-db">LINQ to DB<a class="headerlink" href="#linq-to-db" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<p>TODO</p>
<h2 id="llblgen-pro">LLBLGen Pro<a class="headerlink" href="#llblgen-pro" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : IMultipleCrudScenario&lt;EmployeeEntity&gt;
{
    public void DeleteBatch(IList&lt;EmployeeEntity&gt; employees)
    {
        if(employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using(var adapter = new DataAccessAdapter())
        {
            // Delete the entities directly using an IN (key1, key2...) predicate. 
            adapter.DeleteEntitiesDirectly(typeof(EmployeeEntity),
                                           new RelationPredicateBucket(EmployeeFields.EmployeeKey
                                                                                     .In(employees.Select(e =&gt; e.EmployeeKey).ToList())));
        }
    }


    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if(employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));
        
        using(var adapter = new DataAccessAdapter())
        {
            // Delete the entities directly using an IN (key1, key2...) predicate. 
            adapter.DeleteEntitiesDirectly(typeof(EmployeeEntity),
                                           new RelationPredicateBucket(EmployeeFields.EmployeeKey.In(employeeKeys)));
        }
    }


    public IList&lt;EmployeeEntity&gt; FindByLastName(string lastName)
    {
        using(var adapter = new DataAccessAdapter())
        {
            return new LinqMetaData(adapter).Employee.Where(e =&gt; e.LastName == lastName).ToList();
        }
    }


    public void InsertBatch(IList&lt;EmployeeEntity&gt; employees)
    {
        if(employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        // The collection is disposed as it assigns event handlers to the entities it contains. Keeping the
        // entities around would keep the collection in memory.
        using(var toInsert = new EntityCollection&lt;EmployeeEntity&gt;(employees))
        {
            using(var adapter = new DataAccessAdapter())
            {
                // use batching for inserts. We'll use 100 for a batch size here. 
                // This will send at most 100 inserts at one time to the database
                // in a single DbCommand
                adapter.BatchSize = 100;
                adapter.SaveEntityCollection(toInsert);
            }
        }
    }


    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;EmployeeEntity&gt; employees)
    {
        if(employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using(var toInsert = new EntityCollection&lt;EmployeeEntity&gt;(employees))
        {
            using(var adapter = new DataAccessAdapter())
            {
                // use batching for inserts. We'll use 100 for a batch size here. 
                // This will send at most 100 inserts at one time to the database
                // in a single DbCommand
                adapter.BatchSize = 100;
                adapter.SaveEntityCollection(toInsert);
            }

            return toInsert.Select(e =&gt; e.EmployeeKey).ToList();
        }
    }


    public IList&lt;EmployeeEntity&gt; InsertBatchReturnRows(IList&lt;EmployeeEntity&gt; employees)
    {
        if(employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        // Use a unit of work here. We could have created a new EntityCollection here but as well
        var uow = new UnitOfWork2();
        foreach(var e in employees)
        {
            uow.AddForSave(e);
        }

        using(var adapter = new DataAccessAdapter())
        {
            // use batching for inserts. We'll use 100 for a batch size here. 
            // This will send at most 100 inserts at one time to the database
            // in a single DbCommand
            adapter.BatchSize = 100;
            uow.Commit(adapter);
        }

        // LLBLGen Pro will update entities in-place after an insert, so we can return what we received. 
        return employees;
    }


    public void InsertBatchWithRefresh(IList&lt;EmployeeEntity&gt; employees)
    {
        if(employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using(var toInsert = new EntityCollection&lt;EmployeeEntity&gt;(employees))
        {
            using(var adapter = new DataAccessAdapter())
            {
                // use batching for inserts. We'll use 100 for a batch size here. 
                // This will send at most 100 inserts at one time to the database
                // in a single DbCommand. LLBLGen Pro will update the entity saved
                // with the new PK.
                adapter.BatchSize = 100;
                adapter.SaveEntityCollection(toInsert);
            }
        }
    }


    public void UpdateBatch(IList&lt;EmployeeEntity&gt; employees)
    {
        if(employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using(var toUpdate = new EntityCollection&lt;EmployeeEntity&gt;(employees))
        {
            using(var adapter = new DataAccessAdapter())
            {
                // use batching for updates. We'll use 100 for a batch size here. 
                // This will send at most 100 updates at one time to the database
                // in a single DbCommand
                adapter.BatchSize = 100;
                adapter.SaveEntityCollection(toUpdate);
            }
        }
    }
}
</code></pre>

<h2 id="nhibernate">NHibernate<a class="headerlink" href="#nhibernate" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : IMultipleCrudScenario&lt;Employee&gt;
{
    readonly ISessionFactory m_SessionFactory;

    public MultipleCrudScenario(ISessionFactory sessionFactory)
    {
        m_SessionFactory = sessionFactory;
    }

    public void DeleteBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var session = m_SessionFactory.OpenSession())
        {
            foreach (var employee in employees)
                session.Delete(employee);
            session.Flush();
        }
    }

    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if (employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

        using (var session = m_SessionFactory.OpenSession())
        {
            foreach (var key in employeeKeys)
                session.Delete(new Employee() { EmployeeKey = key });

            session.Flush();
        }
    }

    public IList&lt;Employee&gt; FindByLastName(string lastName)
    {
        using (var session = m_SessionFactory.OpenSession())
            return session.Query&lt;Employee&gt;().Where(ec =&gt; ec.LastName == lastName).ToList();
    }

    public void InsertBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var session = m_SessionFactory.OpenSession())
        {
            foreach (var employee in employees)
                session.Save(employee);
            session.Flush();
        }
    }

    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var session = m_SessionFactory.OpenSession())
        {
            foreach (var employee in employees)
                session.Save(employee);
            session.Flush();
        }

        return employees.Select(x =&gt; x.EmployeeKey).ToList();
    }

    public IList&lt;Employee&gt; InsertBatchReturnRows(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var session = m_SessionFactory.OpenSession())
        {
            foreach (var employee in employees)
                session.Save(employee);
            session.Flush();
        }

        return employees;
    }

    public void InsertBatchWithRefresh(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var session = m_SessionFactory.OpenSession())
        {
            foreach (var employee in employees)
                session.Save(employee);
            session.Flush();
        }
    }

    public void UpdateBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var session = m_SessionFactory.OpenSession())
        {
            foreach (var employee in employees)
                session.Update(employee);
            session.Flush();
        }
    }
}
</code></pre>

<h2 id="repodb">RepoDb<a class="headerlink" href="#repodb" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : BaseRepository&lt;EmployeeSimple, SqlConnection&gt;,
    IMultipleCrudScenario&lt;EmployeeSimple&gt;
{
    public MultipleCrudScenario(string connectionString)
        : base(connectionString, RDB.Enumerations.ConnectionPersistency.Instance)
    { }

    public void DeleteBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        var keys = employees.Select(e =&gt; e.EmployeeKey).AsList();
        Delete(e =&gt; keys.Contains(e.EmployeeKey));
    }

    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if (employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));

        Delete(e =&gt; employeeKeys.Contains(e.EmployeeKey));
    }

    public IList&lt;EmployeeSimple&gt; FindByLastName(string lastName)
    {
        return Query(e =&gt; e.LastName == lastName).AsList();
    }

    public void InsertBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        InsertAll(employees);
    }

    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        InsertAll(employees);

        return employees.Select(e =&gt; e.EmployeeKey).AsList();
    }

    public IList&lt;EmployeeSimple&gt; InsertBatchReturnRows(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        InsertAll(employees);

        return employees;
    }

    public void InsertBatchWithRefresh(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        InsertAll(employees);
    }

    public void UpdateBatch(IList&lt;EmployeeSimple&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        UpdateAll(employees);
    }
}
</code></pre>

<h2 id="servicestack">ServiceStack<a class="headerlink" href="#servicestack" title="Permalink to this headline"><i class="fa fa-link" aria-hidden="true"></i></a></h2>
<pre><code class="cs">public class MultipleCrudScenario : IMultipleCrudScenario&lt;Employee&gt;
{
    private readonly IDbConnectionFactory _dbConnectionFactory;
    public MultipleCrudScenario(IDbConnectionFactory dbConnectionFactory)
    {
        _dbConnectionFactory = dbConnectionFactory;
    }

    public void DeleteBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));

        using (var db = _dbConnectionFactory.OpenDbConnection())
            db.DeleteAll(employees);
    }

    public void DeleteBatchByKey(IList&lt;int&gt; employeeKeys)
    {
        if (employeeKeys == null || employeeKeys.Count == 0)
            throw new ArgumentException($&quot;{nameof(employeeKeys)} is null or empty.&quot;, nameof(employeeKeys));
        
        using (var db = _dbConnectionFactory.OpenDbConnection())
            db.DeleteByIds&lt;Employee&gt;(employeeKeys);
    }

    public IList&lt;Employee&gt; FindByLastName(string lastName)
    {
        using (var db = _dbConnectionFactory.OpenDbConnection())
            return db.Select&lt;Employee&gt;(e =&gt; e.LastName == lastName);
    }

    public void InsertBatch(IList&lt;Employee&gt; employees)
    {
        using (var db = _dbConnectionFactory.OpenDbConnection())
            db.InsertAll(employees);
    }

    public IList&lt;int&gt; InsertBatchReturnKeys(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));
        
        using (var db = _dbConnectionFactory.OpenDbConnection())
            db.SaveAll(employees);

        return employees.Select(e =&gt; e.Id).ToList();
    }

    public IList&lt;Employee&gt; InsertBatchReturnRows(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));
        
        using (var db = _dbConnectionFactory.OpenDbConnection())
            db.SaveAll(employees);

        return employees;
    }

    public void InsertBatchWithRefresh(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));
        
        using (var db = _dbConnectionFactory.OpenDbConnection())
            db.SaveAll(employees);
    }

    public void UpdateBatch(IList&lt;Employee&gt; employees)
    {
        if (employees == null || employees.Count == 0)
            throw new ArgumentException($&quot;{nameof(employees)} is null or empty.&quot;, nameof(employees));
        
        using (var db = _dbConnectionFactory.OpenDbConnection())
            db.UpdateAll(employees);
    }
}
</code></pre>


                    </div>
                </div>
                <footer>
                    <hr />
                    <div role="contentinfo">
The ORM Cookbook. <a href='https://github.com/Grauenwolf/DotNet-ORM-Cookbook' target='_blank'>Visit us at GitHub</a>.
                    </div>
                </footer>
            </div>
        </section>
    </div>
    <script src="js/jquery-2.1.1.min.js"></script>
    <script src="js/modernizr-2.8.3.min.js"></script>
    <script src="js/highlight.pack.js"></script>
    <script src="js/theme.js"></script>

</body>
</html>
